import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;

public class Filesystem {
	private static final Pattern illegalCharacters = Pattern.compile("^[A-Za-z0-9\\-\\._]+$");
	private BlockDevice m_BlockDevice;
	private int currentFolder;

	public Filesystem(BlockDevice p_BlockDevice) {
		m_BlockDevice = p_BlockDevice;
		currentFolder = 0;
		format();
	}

	public String format() {
		byte[] rootBlock = new byte[512];
		rootBlock[0] = (byte) 1;
		rootBlock[1] = (byte) 255;
		m_BlockDevice.writeBlock(0, rootBlock);

		byte[] emptyBlock = new byte[512];
		emptyBlock[0] = (byte) 255;
		for (int i = 1; i < 512; i++) {
			m_BlockDevice.writeBlock(i, emptyBlock);
		}

		return new String("Diskformat successfull");
	}

	public String ls(String[] p_asPath) throws Exception {
		System.out.print("Listing directory");
		int dir;
		if (p_asPath.length > 1 || p_asPath[0] != ".") {
			dir = resolvePathToIndex(p_asPath, p_asPath.length);
		} else {
			dir = currentFolder;
		}
		int[] children = getChildIndices(m_BlockDevice.readBlock(dir));
		String resultStr = "", name = "", type = "";
		byte[] block = null;
		for (int i = 0; i < children.length; i++) {
			block = m_BlockDevice.readBlock(children[i]);
			name = getName(block);
			type = isFolder(block) ? "folder" : "file";
			resultStr += "\n" + name + "\t" + type;
		}

		dumpArray(p_asPath);
		System.out.print("");
		return resultStr;
	}

	public String create(String[] p_asPath, byte[] p_abContents, boolean silent) {
		if (!silent) {
			System.out.print("Creating file ");
			dumpArray(p_asPath);
		}
		int newFileBlockIndex = 255;

		try {
			int parentIndex = currentFolder;
			if (p_asPath.length > 1) {
				parentIndex = resolvePathToIndex(p_asPath, p_asPath.length - 1);
			}

			int[] childIndices = getChildIndices(m_BlockDevice.readBlock(parentIndex));
			for (int i = 0; i < childIndices.length; i++) {
				byte[] block = m_BlockDevice.readBlock(childIndices[i]);
				if (getName(block).equals(p_asPath[p_asPath.length - 1])) {
					return "\nThere is alreade a file with the same name in this location.";
				}
			}

			newFileBlockIndex = getNextEmptyBlockIndex();
			byte[] newFileBlock = new byte[512];

			setParentIndex(parentIndex, newFileBlock);
			setIsFolder(false, newFileBlock);
			setName(p_asPath[p_asPath.length - 1], newFileBlock);
			int size = setFileData(p_abContents, newFileBlockIndex, newFileBlock);
			setSize(size, newFileBlock);
			m_BlockDevice.writeBlock(newFileBlockIndex, newFileBlock);

			int[] updatedChildIndices = new int[childIndices.length + 1];
			int i;
			for (i = 0; i < childIndices.length; i++) {
				updatedChildIndices[i] = childIndices[i];
			}
			updatedChildIndices[i] = newFileBlockIndex;
			byte[] parentBlock = m_BlockDevice.readBlock(parentIndex);
			setChildIndices(updatedChildIndices, parentBlock);
			m_BlockDevice.writeBlock(parentIndex, parentBlock);

			return "\nFile created successfully.";

		} catch (Exception ex) {
			if (newFileBlockIndex != 255) {
				byte[] fileBlock = m_BlockDevice.readBlock(newFileBlockIndex);
				if (fileBlock[0] != -1) {
					int size;
					size = byteToInt(fileBlock[3]);
					if (size == 0) {
						size = 1;
					}

					int currentBlockIndex = newFileBlockIndex;
					int nextBlockIndex = 255;
					byte[] emptyBlock = new byte[512];
					emptyBlock[0] = (byte) 255;

					while (size > 0 || (fileBlock[511] != 0 && fileBlock[511] != -1)) {
						if (fileBlock[511] != 0 && fileBlock[511] != -1) {
							nextBlockIndex = byteToInt(fileBlock[511]);
						}
						if (currentBlockIndex != 255) {
							m_BlockDevice.writeBlock(currentBlockIndex, emptyBlock);
							currentBlockIndex = nextBlockIndex;
							fileBlock = m_BlockDevice.readBlock(currentBlockIndex);
						}
						size--;
					}
				}
			}
			return "\nError: " + ex.getMessage();
		}
	}

	public String cat(String[] p_asPath) throws Exception {
		System.out.print("Dumping contents of file ");
		dumpArray(p_asPath);

		int blockIndex = resolvePathToIndex(p_asPath, p_asPath.length);
		byte[] block = m_BlockDevice.readBlock(blockIndex);
		if (isFolder(block)) {
			return "\nNot a file.";
		}
		byte[] data = getFileData(block);
		String dataString = readDataToString(0, data.length, data);

		return dataString;
	}

	public String save(String p_sPath) {
		System.out.print("Saving blockdevice to file " + p_sPath);

		FileOutputStream stream = null;
		File file;

		try {

			file = new File(p_sPath);
			stream = new FileOutputStream(file);

			// if file doesnt exists, then create it
			if (!file.exists()) {
				file.createNewFile();
			}

			for (int i = 0; i < 250; i++) {
				stream.write(m_BlockDevice.readBlock(i));
			}

			stream.flush();
			stream.close();

		} catch (IOException e) {
			e.printStackTrace();
		}

		return new String("\nFiles have been saved");
	}

	public String read(String p_sPath) {
		System.out.print("Reading file " + p_sPath + " to blockdevice");
		byte[] block = new byte[512];
		FileInputStream stream = null;
		File file;

		try {

			file = new File(p_sPath);
			stream = new FileInputStream(file);

			// if file doesnt exists, then create it
			if (!file.exists()) {
				stream.close();
				return "\nFile does not exist";
			}

			for (int i = 0; i < 250; i++) {
				if (stream.read(block) == -1) {
					stream.close();
					throw new IOException();
				}
				m_BlockDevice.writeBlock(i, block);
			}

			stream.close();

		} catch (IOException e) {
			e.printStackTrace();
			return "\nError when reading file";
		}

		return "\nLocked n Loaded";
	}

	public String rm(String[] p_asPath, boolean silent) throws Exception {
		// TODO: rm .. == error
		if (!silent) {
			System.out.print("Removing ");
			dumpArray(p_asPath);
		}
		int pathIndex = resolvePathToIndex(p_asPath, p_asPath.length), parentIndex = currentFolder;

		if (p_asPath.length > 1) {
			parentIndex = resolvePathToIndex(p_asPath, p_asPath.length - 1);
		}
		byte[] parentBlock = m_BlockDevice.readBlock(parentIndex);
		int[] parentBlockChildIndices = getChildIndices(parentBlock);
		int[] updatedParentBlockChildIndices = new int[parentBlockChildIndices.length - 1];
		for (int i = 0, j = 0; i < parentBlockChildIndices.length; i++) {
			if (parentBlockChildIndices[i] != pathIndex) {
				updatedParentBlockChildIndices[j++] = parentBlockChildIndices[i];
			}
		}
		setChildIndices(updatedParentBlockChildIndices, parentBlock);
		m_BlockDevice.writeBlock(parentIndex, parentBlock);

		recursiveRemove(pathIndex);
		return "Done";
	}

	public String copy(String[] p_asSource, String[] p_asDestination) throws Exception {
		System.out.print("Copying file from ");
		dumpArray(p_asSource);
		System.out.print(" to ");
		dumpArray(p_asDestination);

		int fileIndex = resolvePathToIndex(p_asSource, p_asSource.length);
		int destinationIndex = resolvePathToIndex(p_asDestination, p_asDestination.length-1);

		byte[] fileBlock = m_BlockDevice.readBlock(fileIndex);
		byte[] destinationBlock = m_BlockDevice.readBlock(destinationIndex);

		if (isFolder(fileBlock)) {
			return "\nCannot copy folder.";
		}
		if (!isFolder(destinationBlock)) {
			return "\nDestination must be a folder.";
		}

		byte[] tempBlock = null;
		int[] childIndices = getChildIndices(destinationBlock);

		for (int i = 0; i < childIndices.length; i++) {
			tempBlock = m_BlockDevice.readBlock(childIndices[i]);
			if (!isFolder(tempBlock)) {
				if (getName(tempBlock).equals(p_asSource[p_asSource.length - 1])) {
					return "\nThere is already a file named " + p_asSource[p_asSource.length - 1] + " in the folder " + p_asDestination[p_asDestination.length - 1];
				}
			}
		}

		byte[] copyFileBlock = fileBlock;
		int copyFileBlockIndex = getNextEmptyBlockIndex();
		setParentIndex(destinationIndex, copyFileBlock);
		setName(p_asDestination[p_asDestination.length - 1], copyFileBlock);
		
		int[] updatedChildIndices = new int[childIndices.length + 1];
		System.arraycopy(childIndices, 0, updatedChildIndices, 0, childIndices.length);
		updatedChildIndices[updatedChildIndices.length - 1] = copyFileBlockIndex;
		destinationBlock = m_BlockDevice.readBlock(destinationIndex);
		setChildIndices(updatedChildIndices, destinationBlock);
		m_BlockDevice.writeBlock(destinationIndex, destinationBlock);
		
		int size = getSize(fileBlock);
		if(size > 1){
			byte[] parentBlock = copyFileBlock;
			byte[] childBlock = null;
			int childIndex, parentIndex = copyFileBlockIndex;
			for(int i = 1; i < size; i++) {
				childBlock = m_BlockDevice.readBlock(byteToInt(parentBlock[511]));
				setParentIndex(parentIndex, childBlock);
				childIndex = getNextEmptyBlockIndex();
				parentBlock[511] = (byte) childIndex;
				m_BlockDevice.writeBlock(childIndex, childBlock);
				parentIndex = childIndex;
				parentBlock = childBlock;
			}
		}
		
		m_BlockDevice.writeBlock(copyFileBlockIndex, copyFileBlock);

		return new String("Done");
	}

	public String append(String[] p_asSource, String[] p_asDestination) throws Exception {
		System.out.print("Appending file ");
		dumpArray(p_asSource);
		System.out.print(" to ");
		dumpArray(p_asDestination);

		int sourceFileIndex = resolvePathToIndex(p_asSource, p_asSource.length);
		int destinationFileIndex = resolvePathToIndex(p_asDestination, p_asDestination.length);
		byte[] sourceFile = m_BlockDevice.readBlock(sourceFileIndex);
		byte[] destinationFile = m_BlockDevice.readBlock(destinationFileIndex);

		if (isFolder(sourceFile) || isFolder(destinationFile)) {
			throw new Exception("Append only works with files.");
		}

		if (sourceFileIndex == destinationFileIndex) {
			throw new Exception("Unable to append file to itself.");
		}

		byte[] sourceFileData = getFileData(sourceFile);
		int i = sourceFileData.length - 1;
		while (i >= 0 && sourceFileData[i] == 0) {
			--i;
		}
		sourceFileData = Arrays.copyOf(sourceFileData, i + 1);
		byte[] destinationFileData = getFileData(destinationFile);
		byte[] combinedData = new byte[sourceFileData.length + destinationFileData.length];
		System.arraycopy(sourceFileData, 0, combinedData, 0, sourceFileData.length);
		System.arraycopy(destinationFileData, 0, combinedData, sourceFileData.length, destinationFileData.length);

		rm(p_asDestination, true);

		create(p_asDestination, combinedData, true);

		return new String("Done");
	}

	public String rename(String[] p_asSource, String[] p_asDestination) throws Exception {
		System.out.print("Renaming file ");
		dumpArray(p_asSource);
		System.out.print(" to ");
		dumpArray(p_asDestination);
		System.out.print("");

		String name = p_asDestination[0];
		int blockIndex = resolvePathToIndex(p_asSource, p_asSource.length);
		byte[] block = m_BlockDevice.readBlock(blockIndex);
		setName(name, block);
		m_BlockDevice.writeBlock(blockIndex, block);

		return new String("");
	}

	public String mkdir(String[] p_asPath) throws Exception {
		// TODO: check for duplicate name in parentFolder
		System.out.print("Creating directory ");
		String name = p_asPath[p_asPath.length - 1];
		int parentChildIndex = 0; // the position of the child pointer within the parent's block
		int newFolderIndex = getNextEmptyBlockIndex();
		byte[] newFolder = m_BlockDevice.readBlock(newFolderIndex);
		int parentFolderIndex = resolvePathToIndex(p_asPath, p_asPath.length - 1);
		byte[] parentFolder = m_BlockDevice.readBlock(parentFolderIndex);

		if (Arrays.equals(parentFolder, m_BlockDevice.readBlock(0))) {
			parentChildIndex = 1 + getChildIndices(parentFolder).length;
		} else {
			parentChildIndex = 18 + getChildIndices(parentFolder).length;
		}
		parentFolder[parentChildIndex] = (byte) newFolderIndex;
		parentFolder[parentChildIndex + 1] = (byte) 255;
		setParentIndex(parentFolderIndex, newFolder);
		setIsFolder(true, newFolder);
		setName(name, newFolder);
		newFolder[18] = (byte) 255;
		// setSize(19, newFolder);
		m_BlockDevice.writeBlock(newFolderIndex, newFolder);
		m_BlockDevice.writeBlock(parentFolderIndex, parentFolder);

		dumpArray(p_asPath);
		System.out.print("");
		return new String("");
	}

	public String cd(String[] p_asPath) throws Exception {
		System.out.print("Changing directory to ");

		currentFolder = resolvePathToIndex(p_asPath, p_asPath.length);

		dumpArray(p_asPath);
		System.out.print("");
		return new String(getName(m_BlockDevice.readBlock(currentFolder)));
	}

	public String pwd() throws Exception {
		String path = "";
		byte[] tempBlock = new byte[512];
		int tempIndex = currentFolder;

		while (tempIndex != 0) {
			tempBlock = m_BlockDevice.readBlock(tempIndex);

			if (tempBlock[0] == (byte) 255) {
				throw new Exception("reading empty block");
			}

			path = readDataToString(2, 18, tempBlock) + "/" + path;
			tempIndex = byteToInt(tempBlock[0]);
		}
		path = "./" + path;
		return path;
	}

	private void dumpArray(String[] p_asArray) {
		for (int nIndex = 0; nIndex < p_asArray.length; nIndex++) {
			System.out.print(p_asArray[nIndex] + "=>");
		}
	}

	private void setParentIndex(int parentIndex, byte[] block) throws Exception {
		if (parentIndex == 255) {
			block[0] = (byte) 255;
		} else if (parentIndex < 0 || parentIndex > 250) {
			throw new Exception("parentIndex " + parentIndex + " is outside of bounds");
		}
		block[0] = (byte) parentIndex;
	}

	private void setIsFolder(boolean isFolder, byte[] block) {
		block[1] = (byte) (isFolder ? 1 : 0);
	}

	private void setSize(int nrOfBlocks, byte[] block) throws Exception {
		if (nrOfBlocks < 1 || nrOfBlocks > 249) {
			throw new Exception(nrOfBlocks + " blocks is an impossible size");
		}
		if (isFolder(block)) {
			throw new Exception("\nCant set size on folder.");
		}
		block[2] = (byte) nrOfBlocks;
	}

	private void setName(String name, byte[] block) throws Exception {
		if (name.length() > 16) {
			throw new Exception("name cannot be longer than 16 characters");
		}
		if (name.trim().isEmpty()) {
			throw new Exception("name cannot be empty");
		}
		if (!illegalCharacters.matcher(name).matches() || name == "." || name == "..") {
			throw new Exception(name + " is not a valid UNIX file name");
		}
		int startIndex;
		if (isFolder(block)) {
			startIndex = 2;
		} else {
			startIndex = 3;
		}
		char[] nameArray = name.toCharArray();
		int nameLength = name.length();
		for (int i = 0; i < nameLength; i++) {
			block[startIndex + i] = (byte) nameArray[i];
		}

		for (int i = nameLength; i < 16; i++) {
			block[startIndex + i] = (byte) 255;
		}
	}

	// returns size in number of blocks
	private int setFileData(byte[] data, int blockIndex, byte[] fileBlock) throws Exception {
		int i, size = 1;
		for (i = 0; i < 492 && data[i] != 0; i++) {
			fileBlock[i + 19] = data[i];
		}

		if (i == 492 && data[i] != 0) {
			ArrayList<Integer> blockIndexArray = new ArrayList<Integer>();
			ArrayList<byte[]> blockArray = new ArrayList<byte[]>();
			int parentIndex = blockIndex;
			byte[] parentBlock = fileBlock;

			while (i < data.length && data[i] != 0) {
				int newDataBlockIndex = getNextEmptyBlockIndex();
				parentBlock[511] = (byte) newDataBlockIndex;

				byte[] newDataBlock = new byte[512];
				setParentIndex(parentIndex, newDataBlock);

				int j;
				for (j = 1; j < 511 && i < data.length && data[i] != 0; j++) {
					newDataBlock[j] = data[i];
					i++;
				}
				newDataBlock[j] = (byte) 255;

				blockIndexArray.add(newDataBlockIndex);
				blockArray.add(newDataBlock);

				parentIndex = newDataBlockIndex;
				parentBlock = newDataBlock;

				size++;
			}
			for (int j = 0; j < size - 1; j++) {
				m_BlockDevice.writeBlock(blockIndexArray.get(j), blockArray.get(j));
			}
		} else {
			fileBlock[i + 19] = (byte) 255;
		}
		return size;
	}

	private void setChildIndices(int[] childIndices, byte[] block) throws Exception {
		int startIndex = 18;
		if (Arrays.equals(block, m_BlockDevice.readBlock(0))) { // If root block.
			startIndex = 1;
		}

		if (startIndex != 1 && !isFolder(block)) {
			throw new Exception("a file cannot have children");
		}

		for (int i = 0; i < childIndices.length; i++) {
			if (childIndices[i] < 1 || childIndices[i] > 249) {
				throw new Exception("folder child index is out of bounds");
			}
			block[startIndex + i] = (byte) childIndices[i];
		}

		block[startIndex + childIndices.length] = (byte) 255;
	}

	private int getParentIndex(byte[] block) throws Exception {
		if (Arrays.equals(block, m_BlockDevice.readBlock(0))) {
			return 0;
		}
		return byteToInt(block[0]);
	}

	private boolean isFolder(byte[] block) throws Exception {
		if (Arrays.equals(block, m_BlockDevice.readBlock(0))) { // If root block.
			return true;
		}
		
		if (getParentIndex(block) == 255) {
			throw new Exception("the block is empty");
		}

		int isFolder = byteToInt(block[1]);
		if (isFolder == 1) {
			return true;
		}
		return false;
	}

	// Returns number of blocks.
	private int getSize(byte[] block) throws Exception {
		if (getParentIndex(block) == 255) {
			throw new Exception("\nthe block is empty.");
		}
		if (isFolder(block)) {
			throw new Exception("\nfolder cannot have size.");
		}
		return byteToInt(block[2]);
	}

	private String getName(byte[] block) throws Exception {
		if (getParentIndex(block) == 255) {
			throw new Exception("the block is empty");
		}

		String name = "";
		int nameStart = 3;
		if (isFolder(block)) {
			nameStart = 2;
		}
		for (int i = 0; i < 16 && byteToInt(block[nameStart + i]) != 255; i++) {
			name += (char) block[nameStart + i];
		}
		return name;
	}

	private byte[] getFileData(byte[] block) throws Exception {

		int size = getSize(block);

		if (size == 0) {
			throw new Exception(" getFileData: Size is zero.");
		}

		byte[] data = new byte[size * 510];
		int blockPos, dataPos = 0;

		for (int s = 0; s < size; s++) {
			if (s == 0) {
				blockPos = 19;
			} else {
				blockPos = 1;
			}

			while (blockPos < 511 && block[blockPos] != (byte) 255) {
				data[dataPos] = block[blockPos];
				blockPos++;
				dataPos++;
			}

			if (blockPos == 511 && data[blockPos] != (byte) 255) {
				block = m_BlockDevice.readBlock(byteToInt(block[blockPos]));
			}
		}
		return data;
	}

	private int[] getChildIndices(byte[] block) throws Exception {
		if (byteToInt(block[0]) == 255) {
			throw new Exception("the block is empty");
		}

		int startOfArrayIndex = 18;
		int endOfArrayIndex = 18;
		if (Arrays.equals(block, m_BlockDevice.readBlock(0))) { // If root block.
			startOfArrayIndex = 1;
			endOfArrayIndex = 1;
		}

		while (byteToInt(block[endOfArrayIndex]) != 255) {
			endOfArrayIndex++;
		}

		int[] childIndices = new int[endOfArrayIndex - startOfArrayIndex];

		for (int i = startOfArrayIndex; i < endOfArrayIndex; i++) {
			childIndices[i - startOfArrayIndex] = byteToInt(block[i]);
		}
		return childIndices;
	}

	private int getNextEmptyBlockIndex() throws Exception {
		// Get the current empty block index from root.
		byte[] rootBlock = m_BlockDevice.readBlock(0);
		int currentEmptyBlockIndex = byteToInt(rootBlock[0]);

		int nextEmptyBlockIndex, nrOfBlocksToTraverse;
		if (currentEmptyBlockIndex == 255) { // If the file system was full last time. Check it in it's entirety from the beginning.
			nextEmptyBlockIndex = 0;
			nrOfBlocksToTraverse = 249;
		} else { // If not. Start checking from the last empty block.
			nextEmptyBlockIndex = currentEmptyBlockIndex;
			nrOfBlocksToTraverse = 248;
		}

		boolean foundEmptyBlock = false;
		byte[] tempBlock = new byte[512];

		for (int i = 0; i < nrOfBlocksToTraverse && !foundEmptyBlock; i++) { // Go through all remaining blocks or until an empty block is found.
			nextEmptyBlockIndex++;
			if (nextEmptyBlockIndex == 250) { // Keep the index within the file system's bounds.
				nextEmptyBlockIndex = 1;
			}

			tempBlock = m_BlockDevice.readBlock(nextEmptyBlockIndex);

			if (getParentIndex(tempBlock) == 255) { // Check if a block is empty
				foundEmptyBlock = true;
			}
		}

		if (!foundEmptyBlock) {
			nextEmptyBlockIndex = 255;

			if (currentEmptyBlockIndex == 255) { // Case: If the system was full last time and still is.
				throw new Exception("file system is full. There are no empty blocks");
			}
		}

		rootBlock[0] = (byte) nextEmptyBlockIndex;
		m_BlockDevice.writeBlock(0, rootBlock);

		if (currentEmptyBlockIndex == 255) { // Case: If the system was full last time but an empty block was found.
			currentEmptyBlockIndex = getNextEmptyBlockIndex();
		}
		return currentEmptyBlockIndex;
	}

	private int resolvePathToIndex(String[] p_asPath, int length) throws Exception {
		int[] children = null;
		int nextFolderIndex = currentFolder;
		boolean foundChild = false;
		for (int i = 0; i < length; i++) {
			foundChild = false;
			if (p_asPath[i].equals("..")) {
				nextFolderIndex = getParentIndex(m_BlockDevice.readBlock(nextFolderIndex));
			} else {
				children = getChildIndices(m_BlockDevice.readBlock(nextFolderIndex));
				for (int j = 0; j < children.length && !foundChild; j++) {
					if (getName(m_BlockDevice.readBlock(children[j])).equals(p_asPath[i])) {
						nextFolderIndex = children[j];
						foundChild = true;
					}
				}
				if (!foundChild) {
					throw new Exception("Could not find folder");
				}
			}
		}
		return nextFolderIndex;
	}

	private void recursiveRemove(int blockIndex) throws Exception {
		byte[] emptyBlock = new byte[512];
		emptyBlock[0] = (byte) 255;

		byte[] block = m_BlockDevice.readBlock(blockIndex);
		if (isFolder(block)) {
			int[] childIndices = getChildIndices(block);
			for (int i = 0; i < childIndices.length; i++) {
				recursiveRemove(childIndices[i]);
			}
		} else {
			int size = getSize(block) - 1, nextBlockIndex = byteToInt(block[511]);
			while (size > 0) {
				block = m_BlockDevice.readBlock(nextBlockIndex);
				m_BlockDevice.writeBlock(nextBlockIndex, emptyBlock);
				nextBlockIndex = byteToInt(block[511]);
				size--;
			}
		}
		m_BlockDevice.writeBlock(blockIndex, emptyBlock);
	}

	private String readDataToString(int start, int stop, byte[] data) {
		String s = "";
		for (int i = start; i < stop + 1 && data[i] != 0 && data[i] != -1; i++) {
			s += (char) data[i];
		}
		return s;
	}

	// Signed byte to unsigned int
	private int byteToInt(byte b) {
		if (b < 0) {
			return (int) b + 256;
		}
		return (int) b;
	}
}